package cz.muni.fi.pa165.soccergamesarchive.persistencelayer.daos;

import cz.muni.fi.pa165.soccergamesarchive.businesslayer.dtos.SoccerMatchHeaderDTO;
import cz.muni.fi.pa165.soccergamesarchive.errorhandling.ErrorHandler;
import cz.muni.fi.pa165.soccergamesarchive.persistencelayer.entities.SoccerMatch;
import cz.muni.fi.pa165.soccergamesarchive.persistencelayer.querybuilders.entity.SoccerMatchCriteriaQueryBuilder;
import cz.muni.fi.pa165.soccergamesarchive.validators.ValidatorTest;
import java.util.Date;
import java.util.List;
import java.util.Map;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TemporalType;
import javax.persistence.TypedQuery;

import static cz.muni.fi.pa165.soccergamesarchive.errorhandling.ErrorHandler.createErrorMessage;
import static cz.muni.fi.pa165.soccergamesarchive.validators.SoccerMatchValidator.validate;

/**
 *
 * @author Ondrej Dubovec
 * 
 * <strong>Edited by</strong>: Michal Ďuriš 
 */
@Stateless
public class SoccerMatchDAOImpl implements SoccerMatchDAO {

    @PersistenceContext(unitName = "SoccerGamesArchivePersistenceUnit")
    private EntityManager em;

    private final SoccerMatchCriteriaQueryBuilder queryBuilder = new SoccerMatchCriteriaQueryBuilder(
            SoccerMatch.class);

    public SoccerMatchDAOImpl() {
    }

    public SoccerMatchDAOImpl(EntityManager em) {
        this.em = em;
    }

    public EntityManager getEntityManager() {
        return this.em;
    }

    public void setEntityManager(EntityManager em) {
        this.em = em;
    }

    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    @Override
    public SoccerMatch addSoccerMatch(SoccerMatch soccerMatch) throws
            RuntimeException {

        validate(soccerMatch, ValidatorTest.NULL_TEST,
                ValidatorTest.CORRECT_RECORD_TEST);

        em.persist(soccerMatch);
        return soccerMatch;
    }

    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    @Override
    public SoccerMatch changeSoccerMatch(SoccerMatch soccerMatch) throws
            RuntimeException {

        validate(soccerMatch, ValidatorTest.FULL_TEST);

        return em.merge(soccerMatch);
    }

    @TransactionAttribute(TransactionAttributeType.MANDATORY)
    @Override
    public void deleteSoccerMatch(SoccerMatch soccerMatch) throws
            RuntimeException {

        validate(soccerMatch, ValidatorTest.NULL_TEST);

        em.remove(em.merge(soccerMatch));
    }

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    @Override
    public List<SoccerMatch> getAllSoccerMatches() {

        TypedQuery<SoccerMatch> query = em.createQuery(
                "SELECT s FROM SoccerMatch s", SoccerMatch.class);

        return query.getResultList();
    }

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    @Override
    public List<SoccerMatch> getLimitedNumberOfMatches(int offset, int limit,
                                                       Map<String, Object> filterMap,
                                                       Map<String, Boolean> orderMap)
            throws RuntimeException {

        if (limit < 1 || offset < 0) {
            throw new IllegalArgumentException(createErrorMessage(
                    this.getClass().getName(), ErrorHandler.ILLEGAL_VALUE_ERROR));
        }

        return queryBuilder.createEntitySelectCriteriaQuery(em, filterMap,
                orderMap).setFirstResult(offset).setMaxResults(limit).getResultList();
    }

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    @Override
    public List<SoccerMatchHeaderDTO> getLimitedNumberOfMatchHeaders(int offset,
                                                                     int limit,
                                                                     Map<String, Object> filterMap,
                                                                     Map<String, Boolean> orderMap)
            throws RuntimeException {

        if (offset < 0 || limit <= 0) {
            throw new IllegalArgumentException(createErrorMessage(
                    this.getClass().getName(), ErrorHandler.ILLEGAL_VALUE_ERROR));
        }

        List<SoccerMatchHeaderDTO> headersList = queryBuilder
                .createMatchHeaderSelectCriteriaQuery(em, filterMap, orderMap)
                .setFirstResult(offset).setMaxResults(limit).getResultList();

        for (int i = 1; i <= headersList.size(); i++) {
            headersList.get(i - 1).setId((long) i);
        }

        return headersList;
    }

    @Override
    public int getFilteredSoccerMatchesCount(Map<String, Object> filterMap)
            throws RuntimeException {

        return queryBuilder.createEntityCountCriteriaQuery(em, filterMap)
                .getSingleResult().intValue();
    }

    @Override
    public int getFilteredMatchHeadersCount(Map<String, Object> filterMap)
            throws RuntimeException {

        /*
         * TODO: REIMPLEMENT TO BETTER SOLUTION, HOW TO SIMULATE SQL SELECT
         * COUNT(DISTINCT cast(matchDate as DATE) FROM soccerMatch; , BECAUSE
         * JPA Criteria API cant handle such a think... :( THIS IS A LAME
         * SOLUTION, BUT IT WILL BE TEMPORALY WORKS AND FOR EXAMPLE PURPOSES IS
         * FINE
         *
         */
        return queryBuilder.createMatchHeaderCountCriteriaQuery(em, filterMap)
                .getResultList().size();
    }

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    @Override
    public SoccerMatch getSoccerMatchById(Long id) throws RuntimeException {

        if (id == null || id.intValue() < 1) {
            throw new IllegalArgumentException(createErrorMessage(
                    this.getClass().getName(), ErrorHandler.ILLEGAL_VALUE_ERROR));
        }
        return em.find(SoccerMatch.class, id);
    }

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    @Override
    public List<SoccerMatch> getSoccerMatchesByDate(Date date) throws
            RuntimeException {

        if (date == null) {

            throw new IllegalArgumentException(createErrorMessage(
                    this.getClass().getName(), ErrorHandler.ILLEGAL_VALUE_ERROR));
        }

        TypedQuery<SoccerMatch> query = em.createNamedQuery(
                "SoccerMatch.getSoccerMatchByDate", SoccerMatch.class);

        query.setParameter("soccerMatchDate", date, TemporalType.DATE);

        return query.getResultList();
    }

}
